/*
 * Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
 *
 * This file is part of Loop Habit Tracker.
 *
 * Loop Habit Tracker is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Loop Habit Tracker is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.isoron.uhabits.activities.habits.list.controllers;

import android.support.annotation.*;

import org.isoron.uhabits.models.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.activities.habits.list.views.*;

/**
 * Controller responsible for receiving and processing the events generated by a
 * HabitListView. These include selecting and reordering items, toggling
 * checkmarks and clicking habits.
 */
public class HabitCardListController implements HabitCardListView.Controller
{
    private final Mode NORMAL_MODE = new NormalMode();

    private final Mode SELECTION_MODE = new SelectionMode();

    @NonNull
    private final HabitCardListAdapter adapter;

    @Nullable
    private HabitListener habitListener;

    @Nullable
    private SelectionListener selectionListener;

    @NonNull
    private Mode activeMode;

    public HabitCardListController(@NonNull HabitCardListAdapter adapter)
    {
        this.adapter = adapter;
        this.activeMode = new NormalMode();
    }

    /**
     * Called when the user drags a habit and drops it somewhere. Note that the
     * dragging operation is already complete.
     *
     * @param from the original position of the habit
     * @param to   the position where the habit was released
     */
    @Override
    public void drop(int from, int to)
    {
        if (from == to) return;
        cancelSelection();

        Habit habitFrom = adapter.getItem(from);
        Habit habitTo = adapter.getItem(to);
        adapter.performReorder(from, to);

        if (habitListener != null)
            habitListener.onHabitReorder(habitFrom, habitTo);
    }

    /**
     * Called when the user attempts to perform a toggle, but attempt is
     * rejected.
     */
    @Override
    public void onInvalidToggle()
    {
        if (habitListener != null) habitListener.onInvalidToggle();
    }

    /**
     * Called when the user clicks at some item.
     *
     * @param position the position of the clicked item
     */
    @Override
    public void onItemClick(int position)
    {
        activeMode.onItemClick(position);
    }

    /**
     * Called when the user long clicks at some item.
     *
     * @param position the position of the clicked item
     */
    @Override
    public void onItemLongClick(int position)
    {
        activeMode.onItemLongClick(position);
    }

    /**
     * Called when the selection operation is cancelled externally, by something
     * other than this controller. This happens, for example, when the user
     * presses the back button.
     */
    public void onSelectionFinished()
    {
        cancelSelection();
    }

    /**
     * Called when the user wants to toggle a checkmark.
     *
     * @param habit     the habit of the checkmark
     * @param timestamp the timestamps of the checkmark
     */
    @Override
    public void onToggle(@NonNull Habit habit, long timestamp)
    {
        if (habitListener != null) habitListener.onToggle(habit, timestamp);
    }

    public void setHabitListener(@Nullable HabitListener habitListener)
    {
        this.habitListener = habitListener;
    }

    public void setSelectionListener(@Nullable SelectionListener listener)
    {
        this.selectionListener = listener;
    }

    /**
     * Called when the user starts dragging an item.
     *
     * @param position the position of the habit dragged
     */
    @Override
    public void startDrag(int position)
    {
        activeMode.startDrag(position);
    }

    /**
     * Selects or deselects the item at a given position
     *
     * @param position the position of the item to be selected/deselected
     */
    protected void toggleSelection(int position)
    {
        adapter.toggleSelection(position);
        activeMode = adapter.isSelectionEmpty() ? NORMAL_MODE : SELECTION_MODE;
    }

    /**
     * Marks all items as not selected and finishes the selection operation.
     */
    private void cancelSelection()
    {
        adapter.clearSelection();
        activeMode = new NormalMode();

        if (selectionListener != null) selectionListener.onSelectionFinish();
    }

    public interface HabitListener extends CheckmarkButtonController.Listener
    {
        /**
         * Called when the user clicks a habit.
         *
         * @param habit the habit clicked
         */
        void onHabitClick(@NonNull Habit habit);

        /**
         * Called when the user wants to change the position of a habit on the
         * list.
         *
         * @param from habit to be moved
         * @param to   habit that currently occupies the desired position
         */
        void onHabitReorder(@NonNull Habit from, @NonNull Habit to);
    }

    /**
     * A Mode describes the behaviour of the list upon clicking, long clicking
     * and dragging an item. This depends on whether some items are already
     * selected or not.
     */
    private interface Mode
    {
        void onItemClick(int position);

        boolean onItemLongClick(int position);

        void startDrag(int position);
    }

    public interface SelectionListener
    {
        /**
         * Called when the user changes the list of selected item. This is only
         * called if there were previously selected items. If the selection was
         * previously empty, then onHabitSelectionStart is called instead.
         */
        void onSelectionChange();

        /**
         * Called when the user deselects all items or cancels the selection.
         */
        void onSelectionFinish();

        /**
         * Called after the user selects the first item.
         */
        void onSelectionStart();
    }

    /**
     * Mode activated when there are no items selected. Clicks trigger habit
     * click. Long clicks start selection.
     */
    class NormalMode implements Mode
    {
        @Override
        public void onItemClick(int position)
        {
            Habit habit = adapter.getItem(position);
            if (habitListener != null) habitListener.onHabitClick(habit);
        }

        @Override
        public boolean onItemLongClick(int position)
        {
            startSelection(position);
            return true;
        }

        @Override
        public void startDrag(int position)
        {
            startSelection(position);
        }

        protected void startSelection(int position)
        {
            toggleSelection(position);
            activeMode = SELECTION_MODE;
            if (selectionListener != null) selectionListener.onSelectionStart();
        }
    }

    /**
     * Mode activated when some items are already selected.
     * <p>
     * Clicks toggle item selection. Long clicks select more items.
     */
    class SelectionMode implements Mode
    {
        @Override
        public void onItemClick(int position)
        {
            toggleSelection(position);
            notifyListener();
        }

        @Override
        public boolean onItemLongClick(int position)
        {
            toggleSelection(position);
            notifyListener();
            return true;
        }

        @Override
        public void startDrag(int position)
        {
            toggleSelection(position);
            notifyListener();
        }

        protected void notifyListener()
        {
            if (selectionListener == null) return;

            if (activeMode == SELECTION_MODE)
                selectionListener.onSelectionChange();
            else selectionListener.onSelectionFinish();
        }
    }
}
